經過上篇「Day 12: 了解Trigger Module的神秘面紗(上)~~!」的簡介,相信對Trigger Module的功能及相關Register應該有個簡單的了解了吧!?
今天呢! 為了要準備去跨年,所以我們講點輕鬆的~~!
來看看Debugger如何使用這個Trigger Module當作"Breakpoint"和"Watchpoint"來使用!
[2018/01/06] 修改初始化流程
上篇「Day 12: 了解Trigger Module的神秘面紗(上)~~!」有提到幾個Trigger Module常用的Registers,這邊簡單的回顧一下
終於要來講實作的部分了!
由於每個Trigger都有可能支援各種不同的功能,因此建議初始化的流程如下:
參考以下程式碼(src/target/riscv/riscv.c)
int riscv_enumerate_triggers(struct target *target)
{
RISCV_INFO(r);
for (int hartid = 0; hartid < riscv_count_harts(target); ++hartid) {
if (!riscv_hart_enabled(target, hartid))
continue;
riscv_reg_t tselect = riscv_get_register_on_hart(target, hartid,
GDB_REGNO_TSELECT);
for (unsigned t = 0; t < RISCV_MAX_TRIGGERS; ++t) {
r->trigger_count[hartid] = t;
///譯註: Step 1. 對$tselect寫入目前選擇Trigger的編號
riscv_set_register_on_hart(target, hartid, GDB_REGNO_TSELECT, t);
uint64_t tselect_rb = riscv_get_register_on_hart(target, hartid,
GDB_REGNO_TSELECT);
/* Mask off the top bit, which is used as tdrmode in old
* implementations. */
tselect_rb &= ~(1ULL << (riscv_xlen(target)-1));
///譯註: Step 2. 重新讀回$tselect,確認該Trigger是否存在
if (tselect_rb != t)
break;
///譯註: Step 3. 讀取$tdata1,確認Trigger支援的功能
uint64_t tdata1 = riscv_get_register_on_hart(target, hartid,
GDB_REGNO_TDATA1);
int type = get_field(tdata1, MCONTROL_TYPE(riscv_xlen(target)));
switch (type) {
case 1:
/* On these older cores we don't support software using
* triggers. */
riscv_set_register_on_hart(target, hartid, GDB_REGNO_TDATA1, 0);
break;
case 2:
if (tdata1 & MCONTROL_DMODE(riscv_xlen(target)))
riscv_set_register_on_hart(target, hartid, GDB_REGNO_TDATA1, 0);
break;
}
}
riscv_set_register_on_hart(target, hartid, GDB_REGNO_TSELECT, tselect);
LOG_INFO("[%d] Found %d triggers", hartid, r->trigger_count[hartid]);
}
return ERROR_OK;
}
主要分成三部分(src/target/riscv/riscv.c)
首先是上半部,當GDB傳Breakpoint的資料進來時
if (breakpoint->type == BKPT_SOFT) {
....Software Breakpoint的部分省略,待日後說明~~!
} else if (breakpoint->type == BKPT_HARD) {
struct trigger trigger;
trigger_from_breakpoint(&trigger, breakpoint); ///譯註: 先準備好相關資料
int result = add_trigger(target, &trigger); ///譯註: 新增Trigger
if (result != ERROR_OK)
return result;
} else {
LOG_INFO("OpenOCD only supports hardware and software breakpoints.");
return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
}
breakpoint->set = true;
return ERROR_OK;
再來看一下中間資料準備的過程
static void trigger_from_breakpoint(struct trigger *trigger,
const struct breakpoint *breakpoint)
{
trigger->address = breakpoint->address;
trigger->length = breakpoint->length;
trigger->mask = ~0LL;
trigger->read = false;
trigger->write = false;
trigger->execute = true; ///譯註: 這行很重要,用來標示這是個Breakpoint
/* unique_id is unique across both breakpoints and watchpoints. */
trigger->unique_id = breakpoint->unique_id;
}
最後是新增Trigger的部分,基本上就是把準備好的資料,一個個複製到對應的欄位中!
static int maybe_add_trigger_t2(struct target *target, unsigned hartid,
struct trigger *trigger, uint64_t tdata1)
{
RISCV_INFO(r);
/* tselect is already set */
if (tdata1 & (MCONTROL_EXECUTE | MCONTROL_STORE | MCONTROL_LOAD)) {
/* Trigger is already in use, presumably by user code. */
return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
}
/* address/data match trigger */
tdata1 |= MCONTROL_DMODE(riscv_xlen(target));
tdata1 = set_field(tdata1, MCONTROL_ACTION,
MCONTROL_ACTION_DEBUG_MODE);
tdata1 = set_field(tdata1, MCONTROL_MATCH, MCONTROL_MATCH_EQUAL);
tdata1 |= MCONTROL_M;
if (r->misa & (1 << ('H' - 'A')))
tdata1 |= MCONTROL_H;
if (r->misa & (1 << ('S' - 'A')))
tdata1 |= MCONTROL_S;
if (r->misa & (1 << ('U' - 'A')))
tdata1 |= MCONTROL_U;
if (trigger->execute)
tdata1 |= MCONTROL_EXECUTE;
if (trigger->read)
tdata1 |= MCONTROL_LOAD;
if (trigger->write)
tdata1 |= MCONTROL_STORE;
riscv_set_register_on_hart(target, hartid, GDB_REGNO_TDATA1, tdata1);
uint64_t tdata1_rb = riscv_get_register_on_hart(target, hartid, GDB_REGNO_TDATA1);
LOG_DEBUG("tdata1=0x%" PRIx64, tdata1_rb);
if (tdata1 != tdata1_rb) {
LOG_DEBUG("Trigger doesn't support what we need; After writing 0x%"
PRIx64 " to tdata1 it contains 0x%" PRIx64,
tdata1, tdata1_rb);
riscv_set_register_on_hart(target, hartid, GDB_REGNO_TDATA1, 0);
return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
}
riscv_set_register_on_hart(target, hartid, GDB_REGNO_TDATA2, trigger->address);
return ERROR_OK;
}
一樣主要分成三部分(src/target/riscv/riscv.c)
首先是上半部,當GDB傳Watchpoint的資料進來時
int riscv_add_watchpoint(struct target *target, struct watchpoint *watchpoint)
{
struct trigger trigger;
trigger_from_watchpoint(&trigger, watchpoint); ///譯註: 先準備好相關資料
int result = add_trigger(target, &trigger); ///譯註: 新增Trigger
if (result != ERROR_OK)
return result;
watchpoint->set = true;
return ERROR_OK;
}
在過來看一下資料準備的部分
static void trigger_from_watchpoint(struct trigger *trigger,
const struct watchpoint *watchpoint)
{
trigger->address = watchpoint->address;
trigger->length = watchpoint->length;
trigger->mask = watchpoint->mask;
trigger->value = watchpoint->value;
trigger->read = (watchpoint->rw == WPT_READ || watchpoint->rw == WPT_ACCESS); ///譯註: 這行很重要,用來標示這是個Watchpoint,在資料被讀取時比較
trigger->write = (watchpoint->rw == WPT_WRITE || watchpoint->rw == WPT_ACCESS); ///譯註: 這行很重要,用來標示這是個Watchpoint,在資料被寫入時比較
trigger->execute = false;
/* unique_id is unique across both breakpoints and watchpoints. */
trigger->unique_id = watchpoint->unique_id;
}
最後新增Trigger的部分同上面"# 2.2 新增Breakpoint",不多加贅述
由於Breakpoint和Watchpoint在移除Trigger的方式相同,這邊就統一一起講解比較快XD!
不囉唆,先上程式碼(src/target/riscv/riscv.c)
static int remove_trigger(struct target *target, struct trigger *trigger)
{
RISCV_INFO(r);
int first_hart = -1;
for (int hartid = 0; hartid < riscv_count_harts(target); ++hartid) {
if (!riscv_hart_enabled(target, hartid))
continue;
if (first_hart < 0) {
first_hart = hartid;
break;
}
}
assert(first_hart >= 0);
unsigned int i;
for (i = 0; i < r->trigger_count[first_hart]; i++) {
///譯註: 找到之前對應的那個Trigger的編號
if (r->trigger_unique_id[i] == trigger->unique_id)
break;
}
if (i >= r->trigger_count[first_hart]) {
LOG_ERROR("Couldn't find the hardware resources used by hardware "
"trigger.");
return ERROR_FAIL;
}
LOG_DEBUG("Stop using resource %d for bp %d", i, trigger->unique_id);
for (int hartid = first_hart; hartid < riscv_count_harts(target); ++hartid) {
if (!riscv_hart_enabled(target, hartid))
continue;
riscv_reg_t tselect = riscv_get_register_on_hart(target, hartid, GDB_REGNO_TSELECT);
riscv_set_register_on_hart(target, hartid, GDB_REGNO_TSELECT, i);
///譯註: 將Data全寫成0,這樣這個Trigger就被關閉了!
riscv_set_register_on_hart(target, hartid, GDB_REGNO_TDATA1, 0);
riscv_set_register_on_hart(target, hartid, GDB_REGNO_TSELECT, tselect);
}
r->trigger_unique_id[i] = -1;
return ERROR_OK;
}
幾本上就是在OpenOCD中,對於每個Breakpoint和Watchpoint都會給他一個unique_id,利用比較這個unique_id就可以快速找到這個Breakpoint/Watchpoint所對應的Trigger編號!
找到該編號用就利用以下步驟關閉他:
大概說明完整個Debug System內容的80%了,整體架構和功能也都有做個淺出的介紹,
詳細內容還是需要對照整份文件和程式碼來看會比較清楚。尤其是官方三不五時就在改Coding的方式 (翻桌
突然發覺是不是應該拆成3篇,這樣.....就可以安心去跨年了!!
Hi HelloWorld,
您好,我是初次接觸RISC-V的新手, 您這系列的文章實在是讓像我這樣的初學者獲益良多,感謝無私分享!
不過在您的文章中的srouce code,在我的理解上是著重在對於OpenOCD的介紹,與說明它如何下發指令來透過JTAG與RISC-V IC裡的debug module溝通,而IC中debug module, debug transprot module, debug module interface...等,則是另外IC設計的範疇,當然還是須配合RISC-V External Debug Support文件來實做。
不知道我這樣的理解是否正確,不過這系列文章已有些時日了,如過您有看到還是希望您能幫我解答一下。感謝!
可以這樣理解沒有錯!
這系列文章主要是以軟體的角度,透過JTAG來操作底層的硬體,
沒有涉入IC設計的範圍!
謝謝您的回覆,這樣就解答我的疑惑了。
這系列文章真的寫得很不錯,很適合剛接觸這個領域的人去了解RISC-V的debugging!